package string

import (
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	gub "gitee.com/shibingli/goutils/bytes"
	"github.com/emmansun/gmsm/sm3"
	"golang.org/x/text/width"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"time"
	"unicode"
)

// ToStr *
func ToStr(arg any, timeFormat ...string) string {
	switch v := arg.(type) {
	case int:
		return strconv.Itoa(v)
	case int8:
		return strconv.FormatInt(int64(v), 10)
	case int16:
		return strconv.FormatInt(int64(v), 10)
	case int32:
		return strconv.FormatInt(int64(v), 10)
	case int64:
		return strconv.FormatInt(v, 10)
	case uint:
		return strconv.Itoa(int(v))
	case uint8:
		return strconv.FormatInt(int64(v), 10)
	case uint16:
		return strconv.FormatInt(int64(v), 10)
	case uint32:
		return strconv.FormatInt(int64(v), 10)
	case uint64:
		return strconv.FormatInt(int64(v), 10)
	case string:
		return v
	case []byte:
		return string(v)
	case bool:
		return strconv.FormatBool(v)
	case float32:
		return strconv.FormatFloat(float64(v), 'f', -1, 32)
	case float64:
		return strconv.FormatFloat(v, 'f', -1, 64)
	case time.Time:
		if len(timeFormat) > 0 {
			return v.Format(timeFormat[0])
		}
		return v.Format("2006-01-02 15:04:05")
	case reflect.Value:
		return ToStr(v.Interface(), timeFormat...)
	case fmt.Stringer:
		return v.String()
	default:
		// Check if the type is a pointer by using reflection
		rv := reflect.ValueOf(arg)
		if rv.Kind() == reflect.Ptr && !rv.IsNil() {
			// Dereference the pointer and recursively call ToString
			return ToStr(rv.Elem().Interface(), timeFormat...)
		} else if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {
			// handle slices
			var buf strings.Builder
			buf.WriteString("[") //nolint: revive,errcheck // no need to check error
			for i := 0; i < rv.Len(); i++ {
				if i > 0 {
					buf.WriteString(" ") //nolint: revive,errcheck // no need to check error
				}
				buf.WriteString(ToStr(rv.Index(i).Interface())) //nolint: revive,errcheck // no need to check error
			}
			buf.WriteString("]") //nolint: revive,errcheck // no need to check error
			return buf.String()
		}

		// For types not explicitly handled, use fmt.Sprint to generate a string representation
		return fmt.Sprint(arg)
	}
}

// StringUtils *
type StringUtils string

// Bool *
func (s StringUtils) Bool() (bool, error) {
	v, err := strconv.ParseBool(s.String())
	return v, err
}

// Float32 *
func (s StringUtils) Float32() (float32, error) {
	v, err := strconv.ParseFloat(s.String(), 32)
	return float32(v), err
}

// Float64 *
func (s StringUtils) Float64() (float64, error) {
	return strconv.ParseFloat(s.String(), 64)
}

// Int *
func (s StringUtils) Int() (int, error) {
	v, err := strconv.ParseInt(s.String(), 10, 32)
	return int(v), err
}

// Int8 *
func (s StringUtils) Int8() (int8, error) {
	v, err := strconv.ParseInt(s.String(), 10, 8)
	return int8(v), err
}

// Int16 *
func (s StringUtils) Int16() (int16, error) {
	v, err := strconv.ParseInt(s.String(), 10, 16)
	return int16(v), err
}

// Int32 *
func (s StringUtils) Int32() (int32, error) {
	v, err := strconv.ParseInt(s.String(), 10, 32)
	return int32(v), err
}

// Int64 *
func (s StringUtils) Int64() (int64, error) {
	v, err := strconv.ParseInt(s.String(), 10, 64)
	return v, err
}

// Uint *
func (s StringUtils) Uint() (uint, error) {
	v, err := strconv.ParseUint(s.String(), 10, 32)
	return uint(v), err
}

// Uint8 *
func (s StringUtils) Uint8() (uint8, error) {
	v, err := strconv.ParseUint(s.String(), 10, 8)
	return uint8(v), err
}

// Uint16 *
func (s StringUtils) Uint16() (uint16, error) {
	v, err := strconv.ParseUint(s.String(), 10, 16)
	return uint16(v), err
}

// Uint32 *
func (s StringUtils) Uint32() (uint32, error) {
	v, err := strconv.ParseUint(s.String(), 10, 32)
	return uint32(v), err
}

// Uint64 *
func (s StringUtils) Uint64() (uint64, error) {
	v, err := strconv.ParseUint(s.String(), 10, 64)
	return uint64(v), err
}

// ToTitleLower *
func (s StringUtils) ToTitleLower() string {
	str := strings.ToLower(s.String()[:1]) + s.String()[1:]
	return str
}

// ToTitleUpper *
func (s StringUtils) ToTitleUpper() string {
	str := strings.ToUpper(s.String()[:1]) + s.String()[1:]
	return str
}

// Contains *
func (s StringUtils) Contains(sep string) bool {
	index := strings.Index(s.String(), sep)
	return index > -1
}

// String *
func (s StringUtils) String() string {
	return string(s)
}

func (s StringUtils) Bytes() []byte {
	return gub.UnsafeBytes(s.String())
}

func (s StringUtils) HexDecode() (string, error) {
	b, err := hex.DecodeString(s.String())
	if err != nil {
		return "", err
	}
	return string(b), nil
}

func (s StringUtils) HexEncode() string {
	return hex.EncodeToString(s.Bytes())
}

func (s StringUtils) SM3() string {
	h := sm3.New()

	h.Write(s.Bytes())

	e := h.Sum(nil)

	return hex.EncodeToString(e)
}

// MD5 *
func (s StringUtils) MD5() string {
	m := md5.New()
	m.Write(s.Bytes())
	return hex.EncodeToString(m.Sum(nil))
}

// SHA1 *
func (s StringUtils) SHA1() string {
	sha := sha1.New()
	sha.Write(s.Bytes())
	return hex.EncodeToString(sha.Sum(nil))
}

// SHA256 *
func (s StringUtils) SHA256() string {
	sha := sha256.New()
	sha.Write(s.Bytes())
	return hex.EncodeToString(sha.Sum(nil))
}

// SHA512 *
func (s StringUtils) SHA512() string {
	sha := sha512.New()
	sha.Write(s.Bytes())
	return hex.EncodeToString(sha.Sum(nil))
}

func (s StringUtils) HmacMD5(key string) string {
	mc := hmac.New(md5.New, []byte(key))
	mc.Write(s.Bytes())
	return hex.EncodeToString(mc.Sum(nil))
}

// HmacSHA1 *
func (s StringUtils) HmacSHA1(key string) string {
	mc := hmac.New(sha1.New, []byte(key))
	mc.Write(s.Bytes())
	return hex.EncodeToString(mc.Sum(nil))
}

// HmacSHA256 *
func (s StringUtils) HmacSHA256(key string) string {
	mc := hmac.New(sha256.New, []byte(key))
	mc.Write(s.Bytes())
	return hex.EncodeToString(mc.Sum(nil))
}

// HmacSHA512 *
func (s StringUtils) HmacSHA512(key string) string {
	mc := hmac.New(sha512.New, []byte(key))
	mc.Write(s.Bytes())
	return hex.EncodeToString(mc.Sum(nil))
}

// StdBase64Encode *
func (s StringUtils) StdBase64Encode() string {
	return base64.StdEncoding.EncodeToString(s.Bytes())
}

// URLBase64Encode *
func (s StringUtils) URLBase64Encode() string {
	return base64.URLEncoding.EncodeToString(s.Bytes())
}

// Base64Decode *
func (s StringUtils) Base64Decode() (string, error) {

	inputVal := s.String()

	seg, err := url.PathUnescape(inputVal)
	if nil != err {
		return "", err
	}

	if l := len(seg) % 4; l > 0 {
		seg += strings.Repeat("=", 4-l)
	}
	v, err := base64.StdEncoding.DecodeString(seg)
	return string(v), err
}

// Base64DecodeByte *
func (s StringUtils) Base64DecodeByte() ([]byte, error) {

	inputVal := s.String()

	seg, err := url.PathUnescape(inputVal)
	if nil != err {
		return nil, err
	}

	if l := len(seg) % 4; l > 0 {
		seg += strings.Repeat("=", 4-l)
	}
	v, err := base64.URLEncoding.DecodeString(seg)
	return v, err
}

// SBCCommaOrSemiColonToDBC 替换字符串中的全角逗号和分号为半角逗号
func (s StringUtils) SBCCommaOrSemiColonToDBC() string {
	inputVal := s.String()

	sourceRune := []rune(inputVal)

	for i, r := range sourceRune {
		if unicode.IsPunct(r) {
			if '，' == r || ';' == r || '；' == r {
				sourceRune[i] = ','
			}
		}
	}

	return string(sourceRune)
}

// SBCToDBC 全角字符转换为半角字符
func (s StringUtils) SBCToDBC() string {
	inputVal := s.String()

	return width.Narrow.String(inputVal)
}

// DBCToSBC 半角字符转换为全角字符
func (s StringUtils) DBCToSBC() string {
	inputVal := s.String()

	return width.Widen.String(inputVal)
}

// TrimRightSeparator 半角字符转换为全角字符
func (s StringUtils) TrimRightSeparator() string {
	inputVal := s.String()

	inputVal = strings.TrimSuffix(inputVal, "/")
	inputVal = strings.TrimSuffix(inputVal, "\\")

	return inputVal
}

// VersionSerialize 序列化版本号
func (s StringUtils) VersionSerialize() string {
	ver := strings.TrimSpace(s.String())

	if "" == ver {
		return ""
	}

	return versionSerialize(ver)
}

// LPad 左位补零（"31", 6 => 000031）
func (s StringUtils) LPad(size ...int) string {
	val := strings.TrimSpace(s.String())

	if "" == val {
		return ""
	}

	return lpad(val, size...)
}

func lpad(val string, size ...int) string {

	val = strings.TrimSpace(val)
	if "" == val {
		return ""
	}

	l := 6
	if len(size) > 0 {
		l = size[0]
	}

	return fmt.Sprintf("%0*s", l, val)
}

func versionSerialize(ver string) string {
	ver = strings.TrimSpace(ver)

	if "" == ver {
		return ""
	}

	arr := strings.Split(ver, ".")

	var rv string

	for _, s := range arr {
		rv = rv + lpad(s)
	}

	return rv
}
